package preprocessor.absorption;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import option.KBVocabulary;

import preprocessor.Simplification;
import preprocessor.absorption.extended.AssertionConversion;

import kb.TBox;
//import entities.InclusionAxiom;
import entities.Individual;
import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.concept.ExistsConcept;
import entities.concept.Nominal;
//import entities.concept.NotConcept;
//import entities.concept.OrConcept;
import entities.concept.PConcept;
import entities.concept.UniversalConcept;
import entities.role.Role;

public class RoleAbsorption implements Absorption {

	private TBox tbox;
	private static Map<Role, Concept> absorbedDomanRange = new HashMap<Role, Concept>();
	private static Map<Role, Map<Individual, Set<Individual>>> cachedRAs = new HashMap<Role, Map<Individual, Set<Individual>>>();
	private static boolean setDomainRangeInRole = true;

	public RoleAbsorption(TBox tb) {
		this.tbox = tb;
	}

	private static void applyRAs(TBox tb) {
		for (Role r : cachedRAs.keySet()) {
			for (Individual a : cachedRAs.get(r).keySet()) {
				for (Individual b : cachedRAs.get(r).get(a)) {
					applyRAs(r, a, b, tb);
				}
			}
		}
	}

	// extended absorption for domain and range
	// for a R b, add (a and Def_a) -> C if there is an entry in absorbed
	// domain/range
	// need to add (b and Def_b -> C) for R- as well
	private static void applyRAs(Role r, Individual a, Individual b, TBox tb) {
		if (absorbedDomanRange.containsKey(r)) {
			Concept ac = RoleAbsorption.getDomainOrRange(r);
			if (ac != null) {
				Nominal na = Nominal.getNominal(a.getName(), false);
				PConcept defa = PConcept.newGuardPConcept(na);
				AssertionConversion.binaryAbsorb(na, defa, ac, tb);
			}
		}
		Role ir = tb.getRoleBox().getInverseRole(r);
		if (absorbedDomanRange.containsKey(ir)) {
			Concept bc = RoleAbsorption.getDomainOrRange(ir);
			if (bc != null) {
				Nominal nb = Nominal.getNominal(b.getName(), false);
				PConcept defb = PConcept.newGuardPConcept(nb);
				AssertionConversion.binaryAbsorb(nb, defb, bc, tb);
			}
		}
	}

	public static void cacheRA(Role r, Individual a, Individual b) {
		Map<Individual, Set<Individual>> indmap = null;
		if (cachedRAs.containsKey(r)) {
			indmap = cachedRAs.get(r);
		} else {
			indmap = new HashMap<Individual, Set<Individual>>();
		}
		Set<Individual> fis = null;
		if (indmap.containsKey(a)) {
			fis = indmap.get(a);
		} else {
			fis = new HashSet<Individual>();
		}
		fis.add(b);
		indmap.put(a, fis);
		cachedRAs.put(r, indmap);
	}

	public static Map<Role, Concept> getAbsorbedDomainRangeAxioms() {
		return absorbedDomanRange;
	}

	public static Concept getDomainOrRange(Role r) {
		if (absorbedDomanRange.containsKey(r)) {
			return absorbedDomanRange.get(r);
		}
		return null;
	}

	public boolean absorb() {
		boolean suc = true;
		Set<Concept> cs = new HashSet<Concept>();
		Set<Concept> topadded = new HashSet<Concept>();
		boolean top = false;
		for (Concept c : this.tbox.getTg().keySet()) {
			Concept rhs = this.tbox.getTg().get(c);
			if (c.isTop()) {
				// top -> C1 and C2 and ... and Cn, absorb Ci separately.
				for (Concept each : Concept.reduceToConjuncts(rhs)) {
					boolean part = this.absorbAxiom(c, each);
					if (!part) {
						topadded.add(each);
					} else {
						top = true;
						suc = true;
					}
				}
			} else if (absorbAxiom(c, rhs)) {
				cs.add(c);
				suc = true;
			}
		}
		for (Concept cc : cs) {
			this.tbox.getTg().remove(cc);
		}
		// modify top GCI
		if (top) {
			if (topadded.size() == 1) {
				this.tbox.getTg().put(KBVocabulary.CONCEPT_TOP,
						topadded.iterator().next());
			} else if (topadded.size() > 1) {
				this.tbox.getTg().put(KBVocabulary.CONCEPT_TOP,
						new AndConcept(topadded));
			} else {
				// remove this top GCI
				this.tbox.getTg().remove(KBVocabulary.CONCEPT_TOP);
			}
		}
		// finalize absorption for domain and range
		// if(cachedRAs.size() > 0){
		// //
		// System.out.println("cached RAs to be processed: "+cachedRAs.size());
		// applyRAs(this.tbox);
		// }
		return suc;
	}

	/**
	 * role absorption tailored for assertion absorption. some R.top -> A
	 * (domain) [~A -> all R.bottom] top -> all R.B (range) [some R. ~B ->
	 * bottom]
	 * 
	 * We take range as ~B->all R-.bot => some R-.top->B hence: some R.top -> A
	 * (domain) some R-.top-> B (range of R/domain of R-)
	 * 
	 * @param ax
	 * @return true if axiom ax can be absorbed by this absorption
	 */

	private boolean absorbAxiom(Concept lhs, Concept rhs) {
		// Concept lhs = ax.getLHS(), rhs = ax.getRHS();
		// check domain: some R.top->A, domainR=A, rangeR-=A
		if (lhs instanceof ExistsConcept) {
			ExistsConcept ec = (ExistsConcept) lhs;
			if (ec.getFiller().isTop()) {
				this.addDomanRangeEntry(ec.getRole(), rhs);
				if (setDomainRangeInRole) {
					Role r = ec.getRole();
					Role ir = tbox.getRoleBox().getInverseRole(r);
					r.setDomain(rhs);
					ir.setRange(rhs);
				}
				return true;
			}
		}
		// ~C->all R.bot, domainR=C,rangeR-=C
		if (rhs instanceof UniversalConcept) {
			UniversalConcept uc = (UniversalConcept) rhs;
			if (uc.getFiller().isBot()) {
				Concept domain = Simplification.NNF(lhs, true);
				this.addDomanRangeEntry(uc.getRole(), domain);
				if (setDomainRangeInRole) {
					Role r = uc.getRole();
					Role ir = tbox.getRoleBox().getInverseRole(r);
					r.setDomain(domain);
					ir.setRange(domain);
				}
				return true;
			}
		}
		// check range top->all R.C, rangeR=C, domainR-=C
		if (lhs.isTop() && rhs instanceof UniversalConcept) {
			UniversalConcept uc = (UniversalConcept) rhs;
			Role ir = tbox.getRoleBox().getInverseRole(uc.getRole());
			this.addDomanRangeEntry(ir, uc.getFiller());
			if (setDomainRangeInRole) {
				Role r = uc.getRole();
				r.setRange(uc.getFiller());
				ir.setDomain(uc.getFiller());
			}
			return true;
		}
		// some R.~B->bot, range R=B,domainR-=B
		if (rhs.isBot() && lhs instanceof ExistsConcept) {
			ExistsConcept ec = (ExistsConcept) lhs;
			Concept range = Simplification.NNF(ec.getFiller(), true);
			Role r = ec.getRole();
			Role ir = tbox.getRoleBox().getInverseRole(r);
			this.addDomanRangeEntry(ir, range);
			if (setDomainRangeInRole) {
				r.setRange(range);
				ir.setDomain(range);
			}
			return true;
		}
		return false;
	}

	private void addDomanRangeEntry(Role r, Concept c) {
		Concept tc = c;
		if (absorbedDomanRange.containsKey(r)) {
			tc = new AndConcept(c, absorbedDomanRange.get(r));
		}
		absorbedDomanRange.put(r, tc);
	}

	// //given some R.C -> D, add to domain (all R. ~C or D)
	// //given D -> all R.C, add to domain (~D or all R.C)
	// public boolean absorbAxiom(InclusionAxiom ax){
	// boolean abs = false;
	// if(ax.getLHS() instanceof ExistsConcept){
	// ExistsConcept ex = (ExistsConcept) ax.getLHS();
	// UniversalConcept part1 = new UniversalConcept(ex.getRole(), new
	// NotConcept(ex.getFiller()));
	// HashSet<Concept> ors= new HashSet<Concept>();
	// ors.add(part1);
	// ors.add(ax.getRHS());
	// ex.getRole().setDomain(new OrConcept(ors));
	// abs=true;
	// }else
	// if(ax.getRHS() instanceof UniversalConcept){
	// UniversalConcept uc = (UniversalConcept) ax.getRHS();
	// HashSet<Concept> ors= new HashSet<Concept>();
	// ors.add(new NotConcept(ax.getLHS()));
	// ors.add(uc);
	// uc.getRole().setDomain(new OrConcept(ors));
	// abs=true;
	// }
	// return abs;
	// }

}
